package com.uxsino.simo.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;

import com.uxsino.simo.indicator.expression.ExprEvaluator;
import com.uxsino.simo.indicator.expression.mvel.ExprEvaluatorMVEL;

public class ConfigLoadingContext {
    @SuppressWarnings("unused")
    private Class<?> loaderClass;

    private Logger logger;

    private String currentSource;

    private ConfigPropLoader propLoader;

    private ExprEvaluator evaluator = new ExprEvaluatorMVEL();

    /**
     * 
     * store loading info of a source
     *
     */
    public static final class SourceInfo {
        // location : error msg
        public List<Pair<String, String>> errors = new ArrayList<>();

        // object name : location
        public Map<String, String> objectLocations = new HashMap<String, String>();
    }

    private Map<String, SourceInfo> sources = new HashMap<>();

    public ConfigLoadingContext(Class<?> loaderClass) {
        this.loaderClass = loaderClass;
        logger = LoggerFactory.getLogger(loaderClass);
        propLoader = new ConfigPropLoader(this);
    }

    public void setCurrentSource(String source) {
        this.currentSource = source;
        if (!sources.containsKey(source)) {
            sources.put(source, new SourceInfo());
        }
    }

    private SourceInfo getCurrentInfo() {
        return sources.get(currentSource);
    }

    private void logErrorOnSourceInfo(String sourceName, String sourceLocation, String format, Object[] arguments) {
        SourceInfo src = sources.get(sourceName);
        FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
        String msg = ft.getMessage();

        if (src != null) {
            String newmsg = sourceName + "[" + sourceLocation + "]: " + msg;
            src.errors.add(Pair.of(sourceLocation, msg));
            if (ft.getThrowable() != null) {
                logger.error(newmsg, ft.getThrowable());
            } else {
                logger.error(newmsg);
            }
        } else {
            logger.error(msg);
        }

    }

    private void logError(String sourceLocation, String format, Object[] arguments) {
        FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
        String msg = ft.getMessage();
        if (currentSource != null) {
            String newmsg = currentSource + "[" + sourceLocation + "]: " + msg;
            getCurrentInfo().errors.add(Pair.of(sourceLocation, msg));
            if (ft.getThrowable() != null) {
                logger.error(newmsg, ft.getThrowable());
            } else {
                logger.error(newmsg);
            }
        } else {
            logger.error(msg);
        }
    }

    /**
     * record an error in source file
     */
    public void error(String sourceLocation, String format, Object... arguments) {
        logError(sourceLocation, format, arguments);
    }

    public void addObject(String name, String location) {
        getCurrentInfo().objectLocations.put(name, location);
    }

    /**
     * record an error on given object name
     * @param name
     * @param format
     * @param arguments
     */
    public void objectError(String name, String format, Object... arguments) {
        String location = getCurrentInfo().objectLocations.get(name);
        if (location == null) {
            location = "";
        }
        logError(location, format, arguments);
    }

    public void locateObjectError(String name, String format, Object... arguments) {
        Pair<String, String> location = this.locateObject(name);
        logErrorOnSourceInfo(location.getLeft(), location.getRight(), format, arguments);
    }

    /**
     * get errors
     * @return source name: list<location, error message>
     */
    public Map<String, List<Pair<String, String>>> getErrors() {
        HashMap<String, List<Pair<String, String>>> errorMap = new HashMap<String, List<Pair<String, String>>>(
            sources.size());
        sources.entrySet().stream().forEach(entry -> {
            errorMap.put(entry.getKey(), entry.getValue().errors);
        });

        return errorMap;
    }

    public Map<String, SourceInfo> getInfo() {
        return sources;
    }

    public Pair<String, String> locateObject(String name) {

        for (Map.Entry<String, SourceInfo> entry : sources.entrySet()) {
            String location = entry.getValue().objectLocations.get(name);

            if (location != null)
                return Pair.of(entry.getKey(), location);
        }
        return Pair.of("", "");
    }

    public ExprEvaluator getEvaluator() {
        return evaluator;
    }

    public ConfigPropLoader getPropLoader() {
        return propLoader;
    }
}
